/**
*    Copyright (C) 2008  Chase Kernan 
*    chase.kernan@gmail.com
*
*    This program is free software: you can redistribute it and/or modify
*    it under the terms of the GNU General Public License as published by
*    the Free Software Foundation, either version 3 of the License, or
*    (at your option) any later version.
*
*    This program is distributed in the hope that it will be useful,
*    but WITHOUT ANY WARRANTY; without even the implied warranty of
*    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
*    GNU General Public License for more details.
*
*    You should have received a copy of the GNU General Public License
*    along with this program.  If not, see <http://www.gnu.org/licenses/>.
* 
**/

package com.chasekernan.hxnova.core.players;

import com.chasekernan.hxnova.core.dataholders.Global;
import com.chasekernan.hxnova.core.dataholders.MessageHandler;
import com.chasekernan.hxnova.core.designs.HullDesign;
import com.chasekernan.hxnova.core.fleets.Fleet;
import com.chasekernan.hxnova.core.stars.ProductionQueue;
import com.chasekernan.hxnova.core.stars.Star;
import com.chasekernan.hxnova.utils.Utils;
import com.chasekernan.hxnova.utils.Vector;
import com.chasekernan.hxnova.utils.XmlBuilder;
import com.chasekernan.hxnova.core.designs.HullDesign;
import com.chasekernan.hxnova.core.battles.BattlePlan;
import haxe.xml.Fast;

enum Orders {
    SendMessage(m : Message);
    ChangeProductionQueue(s : Star, pq : ProductionQueue);
    ChangeFleetWaypoints(f : Fleet, waypoint0 : Task, 
            waypoints : Array<Waypoint>);
    ChangeResearch(field : String, percent : Float);
    ChangeDesigns(d : Array<HullDesign>);
    ChangeRelations(r : PlayerRelations);
    ChangeBattlePlan(f : Fleet, design : HullDesign, bp : BattlePlan);
    ChangeDefaultBattlePlan(bp : BattlePlan);
    ChangeBattlePlans(battlePlans : Array<BattlePlan>);
}

typedef FleetManipulation = {
    var location : Vector;
    var newFleets : Array<Fleet>;
};

/**
    This is sent in to the server on every turn by the player and contains all
    of the orders of the player in the order that the client recieves them from
    the user (the rest is to be sorted by the server.
**/
class TurnOrders {
    
    public var orders : Array<Orders>;
    public var fleetManipulations : Array<FleetManipulation>;
    public var year : Int;
    public var player : Player;
    
    /**
        Year defaults to [Global.year] + 1.
    **/
    public function new(?p : Player) {
        orders = new Array();
        fleetManipulations = new Array();
        year = Global.year + 1;
        player = p;
    }
    
    public function addOrder(o : Orders) {
        orders.push(o);
    }
    
    public static var XML_TITLE = "turn_orders";
    
    /**
        Writes a player's turn orders to xml.
        Sample xml:
            TODO: run [writeToXml].
    **/
    public static function writeToXml(to : TurnOrders) : Xml {
        var xml = Xml.parse('<' + XML_TITLE + '></' + XML_TITLE + '>')
                .firstChild();
                
        xml.set("year", Std.string(to.year));
        xml.set("player", Std.string(to.player.id));
        
        var manipXml = new XmlBuilder("fleet_manipulations");
        
        for (manipulation in to.fleetManipulations) {
            var indivXml = new XmlBuilder("fleet_manipulation");
            
            indivXml.addAttributes(manipulation.location, ["x", "y"]);
            for (fleet in manipulation.newFleets) {
                indivXml.addClassObj(fleet);
            }
        }
        
        xml.addChild(manipXml.xml);
                
        for (order in to.orders) {
            var orderXml = Xml.parse("<order></order>").firstChild();
            
            orderXml.set("type", switch(order) {
                
                case SendMessage(m):
                    orderXml.addChild(MessageHandler.writeMessageToXml(m));
                    "send_message";
                    
                case ChangeProductionQueue(s, pq):
                    orderXml.set("star", Std.string(s.id));
                    orderXml.addChild(ProductionQueue.writeToXml(pq));
                    "change_production_queue";
                    
                case ChangeFleetWaypoints(f, waypoint0, waypoints):
                    orderXml.set("fleet", Std.string(f.id));
                    //just write the fleet to xml and steal the waypoints part.
                    var fleetXml = Fleet.writeToXml(f);
                    orderXml.addChild(Utils.getXmlElement(fleetXml, 
                            "waypoint0"));
                    orderXml.addChild(Utils.getXmlElement(fleetXml, 
                            "waypoints"));
                    "change_fleet_waypoints";
                
                case ChangeResearch(field, percent):
                    orderXml.set("field", Std.string(field));
                    orderXml.set("percent", Std.string(percent));
                    "change_research";
                
                case ChangeDesigns(d):
                    for(design in d) 
                        orderXml.addChild(HullDesign.writeToXml(design));    
                    "change_designs";
                    
                case ChangeRelations(r):
                    orderXml.addChild(PlayerRelations.writeToXml(r));
                    "change_relations";
                    
                case ChangeBattlePlan(f, design, bp):
                    orderXml.set("battle_plan", bp.name);
                    orderXml.set("fleet", Std.string(f.id));
                    orderXml.set("design", design.name);
                    "change_battle_plan";
                    
                case ChangeDefaultBattlePlan(bp):
                    orderXml.addChild(BattlePlan.writeToXml(bp));
                    "change_default_battle_plan";
                    
                case ChangeBattlePlans(battlePlans):
                    for (bp in battlePlans) {
                        orderXml.addChild(BattlePlan.writeToXml(bp));
                    }
                    "change_battle_plans";
            });
            
            xml.addChild(orderXml);
        }
        
        return xml;
    }
    
    /**
        Reads a players orders from xml.
        Will throw an error if there are any mistakes.
        Note: this performs almost no checks on the accuracy of the orders (to
        be handled by the server).
    **/
    public static function readFromXml(xml : Xml) : TurnOrders {
        var to = new TurnOrders();
        
        to.year = Std.parseInt(xml.get("year"));
        to.player = Global.players.getPlayerById(Std.parseInt(
                xml.get("player")));
                
        var x = new Fast(xml);
        
        for (elem in x.node.fleet_manipulations.nodes.fleet_manipulation) {
            if (!elem.has.x || !elem.has.y) {
                throw "Location needs to be specified.";
            }
            
            var location = new Vector(Std.parseFloat(elem.att.x), 
                                      Std.parseFloat(elem.att.y));
            
            var fleets = new Array<Fleet>();
            for (fleetXml in elem.nodes.resolve(Fleet.XML_TITLE)) {
                fleets.push(Fleet.readFromXml(fleetXml.x));
            }
            
            to.fleetManipulations.push( {location : location, 
                                         newFleets : fleets} );
        }
        
        for(e in xml.elementsNamed("order")) {
            to.orders.push(switch(e.get("type")) {
                
                case "send_message":
                    SendMessage(MessageHandler.readMessageFromXml(
                            e.firstElement()));
                
                case "change_production_queue":
                    ChangeProductionQueue(
                        Global.stars.getStarById(Std.parseInt(e.get("star"))),
                        ProductionQueue.readFromXml(e.firstElement()));
                        
                case "change_fleet_waypoints":
                    var fleet = Global.fleets.getById(Std.parseInt(
                            e.get("fleet")), to.player.id);
                    var waypoint0 = Fleet.readWaypoint0FromXml(
                            Utils.getXmlElement(e, "waypoint0"), to.player);
                    var waypoints = Fleet.readWaypointsFromXml(
                            Utils.getXmlElement(e, "waypoints"), to.player);
                    ChangeFleetWaypoints(fleet, waypoint0, waypoints);
                
                case "change_research":
                    ChangeResearch(e.get("field"), 
                            Std.parseFloat(e.get("percent")));
                
                case "change_designs":
                    var ds = new Array<HullDesign>();
                    for (design in e.elementsNamed(HullDesign.XML_TITLE)) {
                        ds.push(HullDesign.readFromXml(design));
                    }
                    ChangeDesigns(ds);
                    
                case "change_relations":
                    var r = PlayerRelations.readFromXml(
                            Utils.getXmlElement(e, PlayerRelations.XML_TITLE));
                    ChangeRelations(r);
                    
                case "change_battle_plan":
                    var bp = to.player.getBattlePlanByName(
                            e.get("battle_plan"));
                    var fleet = Global.fleets.getById(
                            Std.parseInt(e.get("fleet")), to.player.id);
                    var design = to.player.getDesignByName(e.get("design"));
                    ChangeBattlePlan(fleet, design, bp);
                    
                case "change_default_battle_plan":
                    var bp = BattlePlan.readFromXml(
                            Utils.getXmlElement(e, BattlePlan.XML_TITLE));
                    ChangeDefaultBattlePlan(bp);
                    
                case "change_battle_plans":
                    var battlePlans = new Array<BattlePlan>();
                    for (bpXml in e.elements()) {
                        battlePlans.push(BattlePlan.readFromXml(bpXml));
                    }
                    ChangeBattlePlans(battlePlans);
                    
            });
        }
        return to;
    }
}              
